<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>函数的call/apply/bind()</title>
  </head>
  <body>
    <!-- 
    1. 区别call()/apply()/bind()
        call(obj)/apply(obj): 调用函数, 指定函数中的this为第一个参数的值
        bind(obj): 返回一个新的函数, 新函数内部会调用原来的函数, 且this为bind()指定的第一参数的值
        注意: 如果obj是null/undefined, this为window
    2. 应用
        call()/bind()应用: 根据伪数组生成真数组
        bind(): react中组件的自定义方法 / vue中的事件回调函数内部
    3. 自定义call()/apply()
        1). 给obj添加一个临时方法, 方法名任意, 值为当前函数
        2). 通过obj调用这个临时方法, 并将接收的参数传入
        3). 删除obj上的这个临时方法属性
    4. 自定义实现bind()
        1). 返回一个新函数
        2). 在新函数内部通过原函数对象的call方法来执行原函数
            指定this为obj
            指定参数为bind调用的参数和后面新函数调用的参数
  -->
    <!-- <script src="./node_modules/atguigu-utils/dist/atguigu-utils.js"></script> -->
    <!-- 自定义 -->
    <script type="text/javascript">
      // ------------------------------原型上添加 call方法
      Function.prototype.call = function (obj, ...args) {
        console.log("使用call方法");
        // 判断如果obj是undefined和null时，obj是window
        if (obj === undefined || obj === null) {
          obj = window;
        }
        // 给obj对象添加一个方法，属性值是调用call的函数
        obj.tempFn = this;
        // 通过对象调用方法 this函数执行，
        const result = obj.tempFn(...args);
        // 删除临时方法
        delete obj.tempFn;
        // 返回函数执行结果
        return result;
      };
      // ----------------------------原型上添加apply方法
      Function.prototype.apply = function (obj, args) {
        console.log("使用apply方法");
        // apply方法和call类似，只是后两位参数，apply是数组形式
        return this.call(obj, ...args);
      };
      // ---------------------------原型上添加bing方法
      Function.prototype.bind = function (obj, ...args1) {
        console.log("使用bind方法");
        // this是obj
        const _this = this;
        // 返回一个新函数
        return function (...args2) {
          // 新函数内部会执行原来函数的就是bind函数，实例依次是args1,args2
          return _this.call(obj, ...args1, ...args2);
        };
      };
    </script>
    <!-- 测试 -->
    <script>
      function fn(a, b) {
        console.log(a, b, this);
      }

      const obj = { m: 1 };

      // fn.call(fn, obj, 2, 3); // 2 3 Object
      // fn.apply(fn, obj, [2, 3]); //2 3 Object
      // fn.call(fn, undefined, 2, 3); //2 3 Window

      fn.bind(fn, obj)(2, 3); //2 3 Object
      fn.bind(fn, obj, 4)(2, 3); //4 2 Object
      fn.bind(fn, undefined, 4)(2, 3); //4 2 Window
    </script>
  </body>
</html>
